/*
 * netops.c
 *
 * Network operations.
 *
 *  Created on: Dec 15, 2010
 */
#include <sys/types.h>
#include <sys/socket.h>

// for perror().
#include <stdio.h>

// for bzero().
#include <strings.h>

// for exit().
#include <stdlib.h>

// for htons() and sockaddr_in.
#include <arpa/inet.h>

// for close().
#include <unistd.h>

// for memcpy().
#include <string.h>

// For gethistbyname().
#include <netdb.h>

// defines EINTR
#include <errno.h>

#include "FunctionTracing.h"

#include "netops.h"

#define BIND_SOCKET 1
#define DO_NOT_BIND_SOCKET 0

/**
 * Private function -- return a nSocketDescriptor, bound if bBind is set.
 *
 *  @param pstReturnSin - loaded with the nPort and szAddress
 */
int AllocateIpMessage(int bBind, int nPort, const char *szAddress,
		int nMsgType, struct sockaddr_in *pstReturnSin) {
	int nSockDescriptor = -1;

	struct sockaddr_in sin;

	TRACEF(6,("    AllocateIpMessage(%d,%d,%s,%d,%p)\n", bBind, nPort, szAddress, nMsgType, pstReturnSin) );

	// AF_INET     -- IPv4 Internet protocols.
	// SOCK_STREAM -- (tcp) Provides sequenced, reliable, two-way, connection-based byte  streams.
	// SOCK_DGRAM  -- (udp)
	nSockDescriptor = socket(AF_INET, nMsgType, 0);
	if (nSockDescriptor == -1) {
		perror("AllocateIpMessage() - call to socket()");
	} // end if socket failed.


	if (nSockDescriptor != -1) {
		TRACEF(6,("    AllocateIpMessage() about to call\n") );
		if (FillInSockAddrStructure(&sin, szAddress, nPort) == -1) {
			close(nSockDescriptor); // Release the resources.
			nSockDescriptor = -1; // Invalidate the descriptor.
		}
	}

	if ((nSockDescriptor != -1) && (bBind == 1)) {
		TRACEF(6,("AllocateIpMessage() about to bind socket %d:\n", nSockDescriptor) );

		// Bind is only done on the Server
		// bind a name to a socket. Needed to assign an address to the socket.
		if (bind(nSockDescriptor, (struct sockaddr *) &sin, sizeof(sin)) == -1) {
			perror("AllocateIpMessage() - call to bind()");
			close(nSockDescriptor); // Release the resources.
			nSockDescriptor = -1; // Invalidate the descriptor.
		} else {
			TRACEF(6,("AllocateIpMessage() bound socket %d:\n", nSockDescriptor) );

		}
	}

	if (pstReturnSin != NULL) {
		TRACEF(6,("memcpy() to be called.\n") );
		memcpy(pstReturnSin, &sin, sizeof(sin));
	}
	return (nSockDescriptor);
} //


/**
 * Create the TCP network server.
 * The server will listen on any nic card.
 *
 * @param nPort -- port number for server.
 * @return -1 on failure otherwise the handler for the socket.
 *
 * TODO N verify that the nPort is within limits.
 */
int CreateTcpMessage(int nPort) {
	int nSockDescriptor = -1;

	// INADDR_ANY; I think it means it will bind to all available NICs.
	// SOCK_STREAM; TCP communication.
	nSockDescriptor = AllocateIpMessage(BIND_SOCKET, nPort, "0.0.0.0",
			SOCK_STREAM, NULL);

	if (nSockDescriptor != -1) {
		// TODO N Do I want to do a listen here?,
		// listen for connections on a socket
		if (listen(nSockDescriptor, 20) == -1) {
			perror("CreateTcpMessage() - call to listen()");
			close(nSockDescriptor); // Release the resources.
			nSockDescriptor = -1; // Invalidate the descriptor.
		} else {
			TRACEF(5,("Accepting connections ...\n"));
		}
	}

	return (nSockDescriptor);
} // end createtcpmessage.


/**
 * Create a and UDP server.
 * @param nPort - port number.
 * @return Socket descriptior.
 * TODO N Add support for an optional IP address to bind to.
 */
int CreateUdpMessage(int nPort) {
	int nSockDescriptor = -1;

	// INADDR_ANY; I think it means it will bind to all available NICs.
	// SOCK_STREAM; TCP communication.
	nSockDescriptor = AllocateIpMessage(BIND_SOCKET, nPort, "0.0.0.0",
			SOCK_DGRAM, NULL);

	return (nSockDescriptor);
} // end CreateUdpMessage.


/**
 * Close the socket, given in nSockDescriptor.
 */
int CloseSocket(int nSockDescriptor) {
	int nRc = -1;

	nRc = close(nSockDescriptor);
	if (nRc == -1) {
		perror("DestroyTcpMessage() -- close()");
	} // endif nrc	 == -1

	return (nRc);
} // end destroytcpmessage


/**
 * Fill in the pstSockAddr with the given data.
 * @param pstSockAddr -- (out) Pointer to the struct sockaddr_in.
 * @param nPort -- port number. is max 16nnn ?
 * @param szAddress -- Either a hostname of a dotIP address. e.g. 192.168.0.18
 * @return 0 if ok, -1 if fail.
 */
int FillInSockAddrStructure(struct sockaddr_in* pstSockAddr, const char *szAddress, int nPort) {
	int nRc = 0;

	// list of hosts returned by gethostbyname.
	struct hostent *stHostList;
	// Zero the struct. Make sure we start with a clean slate.
	bzero(pstSockAddr, sizeof(struct sockaddr_in));

	TRACEF(6,("    FillInSockAddrStructure(%s,%d,%p)\n", szAddress, nPort, pstSockAddr) );

	// Returns information about the host named name. If the lookup fails, it returns a null pointer.
	// TODO V This is an obsolete function and should be replaces by getaddrinfo()
	stHostList = gethostbyname(szAddress);
	if (stHostList == NULL) {
		herror("AllocateIpMessage() -- gethostbyname() failed.");
		nRc = -1;
	} else {
		TRACEF(6,("gethostbyname() stHostList->h_addr_list[0] is length  %d\n", stHostList->h_length) );
	}

	if (nRc != -1) {
		if (stHostList -> h_addrtype != AF_INET) {
			// TODO N This should be printed to stderr.
			puts("NO Addess type of type: AF_INET from gethostbyname.");
			nRc = -1;
		}
	}

	pstSockAddr->sin_family = AF_INET;
	// TODO V Shouldn't this have HTONL?
	memcpy(&(pstSockAddr->sin_addr), stHostList->h_addr_list[0],
			stHostList->h_length);
	pstSockAddr->sin_port = htons(nPort);
	// htons - convert values between host and network byte order.

	return (nRc);
} // end FillInSockAddrStructure


/**
 * Open a UDP and connect to a server.
 *  @param szAddress -- The address to connect to. It can be a host name of dotted IP address.
 *  @param nPort -- the port address to connect to in the other end (szAddress)
 *  @param pstReturnSockAddr -- set this to NULL if you do not need it, or provide a valid pointer.
 *
 *  @return nSockDescriptor
 *  TODO V Do a common function with CreateTcpMessage.
 */
int OpenUdpMessage(const char *szAddress, int nPort, struct sockaddr_in* pstReturnSockAddr) {
	int nSockDescriptor = -1;

	TRACEF(6,("    OpenUdpMessage(%s,%d,%p)\n", szAddress, nPort, pstReturnSockAddr) );

	// INADDR_ANY; I think it means it will bind to all available NICs.
	// SOCK_STREAM; TCP communication.
	nSockDescriptor = AllocateIpMessage(DO_NOT_BIND_SOCKET, nPort, szAddress,
			SOCK_DGRAM, pstReturnSockAddr);
	TRACEF(6,("   OpenUdpMessage() returns socket descriptor %d \n", nSockDescriptor));
	return (nSockDescriptor);
} // end open tcp message


/**
 * Open a TCP and connect to a server.
 *  TODO V Do a common function with CreateTcpMessage.
 *
 *  @param nPort -- the port address to connect to in the other end (szAddress)
 *  @param szAddress -- The address to connect to. It can be a host name of dotted IP address.
 */
int OpenTcpMessage(int nPort, const char *szAddress) {
	int nSockDescriptor = -1;

	struct sockaddr_in sin;

	// INADDR_ANY; I think it means it will bind to all available NICs.
	// SOCK_STREAM; TCP communication.
	nSockDescriptor = AllocateIpMessage(DO_NOT_BIND_SOCKET, nPort, szAddress,
			SOCK_STREAM, &sin);

	if (nSockDescriptor != -1) {
		TRACEF(6,("Connect() --> %s, %d\n",szAddress, nPort) );
		// bind a name to a socket. Needed to assign an address to the socket.
		if (connect(nSockDescriptor, (struct sockaddr *) &sin, sizeof(sin))
				== -1) {
			perror("OpenTcpMessage() - call to connect()");
			close(nSockDescriptor); // Release the resources.
			nSockDescriptor = -1; // Invalidate the descriptor.
		} else {
			TRACEF(5,("Connected ...\n"));
		}
	}

	return (nSockDescriptor);
} // end open tcp message

int ReceiveTcpMessage(int nSockDescriptor, char *szMessage, int nMaxLength,
		int nRecoverOnIntr) {
	int nRc = -1;

	return (nRc);
} // end receivetcpmessage.


/**
 * @param szMessage -- pointer to a pre-allocated char array.
 * @param nMaxLength -- the allocation size of szMessage.
 * @param nRecoverOnIntr -- if set, then retry read if EINTR caused the receive break.
 *
 * @return nRc -- Returns the length of the message, or -1 on error, -2 if read was interrupted with EINTR.
 */
ssize_t ReceiveUdpMessage(int nSockDescriptor, char *szMessage, int nMaxMessageLength, int nRecoverOnIntr) {
	ssize_t nRc = 0;
	int nReceiveFlags = 0;

	do {
		// Receive a message, don't care about where the msg came from(NULL, NULL).
		nRc = recvfrom(nSockDescriptor, szMessage, nMaxMessageLength, nReceiveFlags, NULL, NULL);
	} while (nRc == -1 && errno == EINTR && nRecoverOnIntr);
	if (nRc == -1) {
		if (errno == EINTR) {
			TRACEF(7,("ReceiveUdpMessage(): recvfrom() interrupted."));
			nRc = -2;
		} else {
			perror("recvfrom()");
		}
	} else {// endif msggid == -1
		TRACEF(7,("ReceiveUdpMessage(%u) recieved: '%s' (%u of %d)\n", (unsigned int)nRc,
				szMessage,(unsigned int)strlen(szMessage), nMaxMessageLength));
	}
	return (nRc);
} // end receiveudpmessage.


/**
 * Close the socket, given in nSockDescriptor.
 */
int ReleaseIpMessage(int nSockDescriptor) {
	int nRc = -1;

	nRc = close(nSockDescriptor);
	if (nRc == -1) {
		perror("ReleaseIpMessage() -- close()");
	} // endif nrc	 == -1

	return (nRc);
} // end ReleaseIpMessage


/**
 *
 * @param pstSockAddr -- Address to send to.
 * @param nSockDescriptor -- Socket to transmit message on.
 * @param szMessage -- Message to be sent.
 * @param nMsgLength -- Length of szMessage including terminating '\0'
 *
 * @note This function will block if the message queue is full.
 * @return -1 on error, 0 on ok.
 *
 * TODO V Should the szMessage be defined as UINT_8?
 */
ssize_t SendUdpMessage(struct sockaddr_in *pstSockAddr, int nSockDescriptor, const char *szMessage, size_t nMsgLength) {
	ssize_t nRc = 0;
	int nFlags = 0;

	do {
		// TODO N Find out why the type case from sockaddr_in to sockaddr?
		nRc = sendto(nSockDescriptor, szMessage, nMsgLength, nFlags, (struct sockaddr*)pstSockAddr, sizeof(struct sockaddr_in));
		TRACEF(7,("   sendto() sent %zd of %zd '%s'\n", nRc, nMsgLength, szMessage));
		// TODO V Support nRecoverOnIntr.
	} while (nRc == -1 && errno == EINTR);
	if (nRc == -1) {
		perror("sendto()");
		TRACEF(7,("SendUdpMessage(): sendto(%d, \"%s\", %u, %d, %p, %u) failed.", nSockDescriptor, szMessage, (unsigned int)nMsgLength, nFlags, pstSockAddr, sizeof(struct sockaddr_in)));
		// TODO V Change this to something else than exit.
		//abort();
	} else {
		// if sento succeeded then nRc holds the number of bytes sent, but this function returns 0 on success.
		nRc = 0;
	}
	return (nRc);
} // end send mq message.
